/**
 * The NativeRelaySignal class is used to relay events from an IEventDispatcher
 * to listeners.
 * The difference as compared to NativeSignal is that
 * NativeRelaySignal has its own dispatching code,
 * whereas NativeSignal uses the IEventDispatcher to dispatch.
 */
package org.osflash.signals.natives;

import org.osflash.signals.ISlot;
import org.osflash.signals.Signal;
import org.osflash.signals.Slot;
import org.osflash.signals.SlotList;
import flash.errors.ArgumentError;
import flash.events.Event;
import flash.events.IEventDispatcher;

class NativeRelaySignal extends Signal, implements INativeDispatcher {
	public var target(getTarget, setTarget) : IEventDispatcher;
	public var eventType(getEventType, setEventType) : String;
	public var eventClass(getEventClass, setEventClass) : Class<Dynamic>;

	var _target : IEventDispatcher;
	var _eventType : String;
	var _eventClass : Class<Dynamic>;
	/**
	 * Creates a new NativeRelaySignal instance to relay events from an IEventDispatcher.
	 * @param	target	An object that implements the flash.events.IEventDispatcher interface.
	 * @param	eventType	The event string name that would normally be passed to IEventDispatcher.addEventListener().
	 * @param	eventClass An optional class reference that enables an event type check in dispatch().
	 * Because the target is an IEventDispatcher,
	 * eventClass needs to be flash.events.Event or a subclass of it.
	 */
	public function new(target : IEventDispatcher, eventType : String, eventClass : Class<Dynamic> = null) {
		super((eventClass != null) ? eventClass : Event);
		this.eventType = eventType;
		this.eventClass = eventClass;
		this.target = target;
	}

	/** @inheritDoc */
	public function getTarget() : IEventDispatcher {
		return _target;
	}

	public function setTarget(value : IEventDispatcher) : IEventDispatcher {
		if(value == _target) return;
		if(_target != null) removeAll();
		_target = value;
		return value;
	}

	/** @inheritDoc */
	public function getEventType() : String {
		return _eventType;
	}

	public function setEventType(value : String) : String {
		_eventType = value;
		return value;
	}

	/** @inheritDoc */
	public function getEventClass() : Class<Dynamic> {
		return _eventClass;
	}

	public function setEventClass(value : Class<Dynamic>) : Class<Dynamic> {
		_eventClass = (value != null) ? value : Event;
		_valueClasses = [_eventClass];
		return value;
	}

	override public function setValueClasses(value : Array<Dynamic>) : Array<Dynamic> {
		eventClass = (value != null && value.length > 0) ? value[0] : null;
		return value;
	}

	/**
	 * @inheritDoc
	 * @throws flash.errors.IllegalOperationError <code>IllegalOperationError</code>: You cannot addOnce() then add() the same listener without removing the relationship first.
	 * @throws ArgumentError <code>ArgumentError</code>: Given listener is <code>null</code>.
	 * @throws ArgumentError <code>ArgumentError</code>: Target object cannot be <code>null</code>.
	 */
	override public function add(listener : Dynamic) : ISlot {
		return addWithPriority(listener);
	}

	/**
	 * @inheritDoc
	 * @throws flash.errors.IllegalOperationError <code>IllegalOperationError</code>: You cannot addOnce() then add() the same listener without removing the relationship first.
	 * @throws ArgumentError <code>ArgumentError</code>: Given listener is <code>null</code>.
	 * @throws ArgumentError <code>ArgumentError</code>: Target object cannot be <code>null</code>.
	 */
	override public function addOnce(listener : Dynamic) : ISlot {
		return addOnceWithPriority(listener);
	}

	/**
	 * @inheritDoc
	 * @throws flash.errors.IllegalOperationError <code>IllegalOperationError</code>: You cannot addOnce() then add() the same listener without removing the relationship first.
	 * @throws ArgumentError <code>ArgumentError</code>: Given listener is <code>null</code>.
	 * @throws ArgumentError <code>ArgumentError</code>: Target object cannot be <code>null</code>.
	 */
	public function addWithPriority(listener : Dynamic, priority : Int = 0) : ISlot {
		return registerListenerWithPriority(listener, false, priority);
	}

	/**
	 * @inheritDoc
	 * @throws flash.errors.IllegalOperationError <code>IllegalOperationError</code>: You cannot addOnce() then add() the same listener without removing the relationship first.
	 * @throws ArgumentError <code>ArgumentError</code>: Given listener is <code>null</code>.
	 * @throws ArgumentError <code>ArgumentError</code>: Target object cannot be <code>null</code>.
	 */
	public function addOnceWithPriority(listener : Dynamic, priority : Int = 0) : ISlot {
		return registerListenerWithPriority(listener, true, priority);
	}

	/** @inheritDoc */
	override public function remove(listener : Dynamic) : ISlot {
		var nonEmptyBefore : Bool = slots.nonEmpty;
		var slot : ISlot = super.remove(listener);
		if(nonEmptyBefore != slots.nonEmpty)
			target.removeEventListener(eventType, onNativeEvent);
		return slot;
	}

	/**
	 * @inheritDoc
	 */
	override public function removeAll() : Void {
		if(target) target.removeEventListener(eventType, onNativeEvent);
		super.removeAll();
	}

	/**
	 * @inheritDoc
	 * @throws ArgumentError <code>ArgumentError</code>: Event object expected.
	 * @throws ArgumentError <code>ArgumentError</code>: No more than one Event object expected.
	 * @throws ArgumentError <code>ArgumentError</code>: Target object cannot be <code>null</code>.
	 * @throws ArgumentError <code>ArgumentError</code>: Event object cannot be <code>null</code>.
	 * @throws ArgumentError <code>ArgumentError</code>: Event object [event] is not an instance of [eventClass].
	 * @throws ArgumentError <code>ArgumentError</code>: Event object has incorrect type. Expected [eventType] but was [event.type].
	 */
	override public function dispatch() : Void {
		if(null == valueObjects) throw new ArgumentError("Event object expected.");
		if(valueObjects.length != 1) throw new ArgumentError("No more than one Event object expected.");
		dispatchEvent(try cast(valueObjects[0], Event) catch(e:Dynamic) null);
	}

	/**
	 * Unlike other signals, NativeRelaySignal does not dispatch null
	 * because it causes an exception in EventDispatcher.
	 * @inheritDoc
	 * @throws ArgumentError <code>ArgumentError</code>: Target object cannot be <code>null</code>.
	 * @throws ArgumentError <code>ArgumentError</code>: Event object cannot be <code>null</code>.
	 * @throws ArgumentError <code>ArgumentError</code>: Event object [event] is not an instance of [eventClass].
	 * @throws ArgumentError <code>ArgumentError</code>: Event object has incorrect type. Expected [eventType] but was [event.type].
	 */
	public function dispatchEvent(event : Event) : Bool {
		if(target == null) throw new ArgumentError("Target object cannot be null.");
		if(event == null) throw new ArgumentError("Event object cannot be null.");
		if(!(Std.is(event, eventClass))) throw new ArgumentError("Event object " + event + " is not an instance of " + eventClass + ".");
		if(event.type != eventType) throw new ArgumentError("Event object has incorrect type. Expected <" + eventType + "> but was <" + event.type + ">.");
		return target.dispatchEvent(event);
	}

	function onNativeEvent(event : Event) : Void {
		var slotsToProcess : SlotList = slots;
		while(slotsToProcess.nonEmpty) {
			slotsToProcess.head.execute1(event);
			slotsToProcess = slotsToProcess.tail;
		}

	}

	function registerListenerWithPriority(listener : Dynamic, once : Bool = false, priority : Int = 0) : ISlot {
		if(target == null) throw new ArgumentError("Target object cannot be null.");
		var nonEmptyBefore : Bool = slots.nonEmpty;
		var slot : ISlot = null;
		if(registrationPossible(listener, once))  {
			slot = new Slot(listener, this, once, priority);
			slots = slots.insertWithPriority(slot);
		}

		else slot = slots.find(listener);
		// Account for cases where the same listener is added twice.
		if(nonEmptyBefore != slots.nonEmpty) target.addEventListener(eventType, onNativeEvent, false, priority);
		return slot;
	}

}

